﻿

using System;
using System.Threading;
using System.Collections.Concurrent;

namespace PECommon {
    /// <summary>
    /// 毫秒级精确的定时器:
    /// 使用外部循环调用或单独计时线程驱动，可设置计时线程回调或外部循环调用时回调。
    /// </summary>
    public class TickTimer : PETimer {
        private class TickTaskPack {
            public int tid;
            public Action<int> cb;
            public TickTaskPack(int tid, Action<int> cb) {
                this.tid = tid;
                this.cb = cb;
            }
        }

        private readonly bool setHandle;
        private readonly ConcurrentDictionary<int, TickTask> taskDic;
        private readonly ConcurrentQueue<TickTaskPack> packQue;
        private const string tidLock = "TickTimer_tidLock";

        private readonly Thread timerThread;
        private readonly DateTime startDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
        /// <summary>
        /// 构造毫秒级精确定时器：
        /// 可使用外部循环调用或单独计时线程驱动，可设置计时线程回调或外部循环调用时回调。
        /// </summary>
        /// <param name="interval">
        /// 大于0：通过定时器内部创建独立线程驱动，驱动时间间隔为这个数值；
        /// 等于0：通过外部线程调用定时器的UpdateTask()函数驱动定时器。
        /// </param>
        /// <param name="setHandle">
        /// false:任务回调由驱动线程触发；
        /// true:任务回调必须由外部线程通过HandleTask()函数触发
        /// </param>
        /// TODO:将setHandle改为传入函数处理回调的Action
        public TickTimer(int interval = 0, bool setHandle = true) {
            taskDic = new ConcurrentDictionary<int, TickTask>();
            this.setHandle = setHandle;
            if (setHandle) packQue = new ConcurrentQueue<TickTaskPack>();

            if (interval != 0) {
                void StartTick() {
                    try {
                        while (true) {
                            UpdateTask();
                            Thread.Sleep(interval);
                        }
                    }
                    catch (ThreadAbortException e) {
                        WarnFunc?.Invoke($"Tick Thread Abort:{e}");
                    }
                }
                timerThread = new Thread(new ThreadStart(StartTick));
                timerThread.Start();
            }
        }

        public override int AddTask(
            uint delay,
            Action<int> taskCB,
            Action<int> cancelCB = null,
            int count = 1) {
            int tid = GenerateTid();
            double startTime = GetUTCMilliseconds();
            double destTime = startTime + delay;
            TickTask task = new TickTask(tid, delay, count, destTime, taskCB, cancelCB, startTime);
            if (taskDic.TryAdd(tid, task)) {
                return tid;
            }
            else {
                WarnFunc?.Invoke($"key：{tid} already exist.");
                return -1;
            }
        }
        public override bool DeleteTask(int tid) {
            if (taskDic.TryRemove(tid, out TickTask task)) {
                if (setHandle && task.cancelCB != null) {
                    packQue.Enqueue(new TickTaskPack(task.tid, task.cancelCB));
                }
                else {
                    task.cancelCB?.Invoke(tid);
                }
                return true;
            }
            else {
                WarnFunc?.Invoke($"tid:{tid} is not exist.");
                return false;
            }
        }
        public override void Reset() {
            if (packQue != null && !packQue.IsEmpty) {
                WarnFunc?.Invoke("Callback Quene is not Empty.");
            }
            taskDic.Clear();
            if (timerThread != null) {
                timerThread.Abort();
            }
        }
        public void UpdateTask() {
            double nowTime = GetUTCMilliseconds();
            foreach (var item in taskDic) {
                TickTask task = item.Value;
                if (nowTime < task.destTime) {
                    continue;
                }
                ++task.loopIndex;
                if (task.count > 0) {
                    --task.count;
                    if (task.count == 0) {
                        FinishTask(task.tid);
                    }
                    else {
                        task.destTime = task.startTime + task.delay * (task.loopIndex + 1);
                        CallTaskCB(tid, task.taskCB);
                    }
                }
                else {
                    //task.count==0，永久循环任务，直到进程退出或调用任务删除
                    task.destTime = task.startTime + task.delay * (task.loopIndex + 1);
                    CallTaskCB(tid, task.taskCB);
                }
            }
        }
        public void HandleTask() {
            while (packQue != null && packQue.Count > 0) {
                if (packQue.TryDequeue(out TickTaskPack pack)) {
                    pack.cb?.Invoke(pack.tid);
                }
                else {
                    ErrorFunc?.Invoke("packQue Dequeue Data Error.");
                }
            }
        }

        void FinishTask(int tid) {
            //线程安全字典，遍历过程中删除无影响
            if (taskDic.TryRemove(tid, out TickTask task)) {
                CallTaskCB(tid, task.taskCB);
                task.taskCB = null;
            }
            else {
                ErrorFunc?.Invoke($"Remove tid:{tid} task in Dic Failed.");
            }
        }
        void CallTaskCB(int tid, Action<int> taskCB) {
            if (setHandle) {
                packQue.Enqueue(new TickTaskPack(tid, taskCB));
            }
            else {
                taskCB.Invoke(tid);
            }
        }
        double GetUTCMilliseconds() {
            TimeSpan ts = DateTime.UtcNow - startDateTime;
            return ts.TotalMilliseconds;
        }

        protected override int GenerateTid() {
            lock (tidLock) {
                while (true) {
                    ++tid;
                    if (tid == int.MaxValue) {
                        tid = 0;
                    }
                    if (!taskDic.ContainsKey(tid)) {
                        return tid;
                    }
                }
            }
        }

        class TickTask {
            public int tid;
            public uint delay;
            public int count;
            public double destTime;//单位：毫秒
            public Action<int> taskCB;
            public Action<int> cancelCB;

            public double startTime;
            public ulong loopIndex;
            public TickTask(
                int tid,
                uint delay,
                int count,
                double destTime,
                Action<int> taskCB,
                Action<int> cancelCB,
                double startTime) {
                this.tid = tid;
                this.delay = delay;
                this.count = count;
                this.destTime = destTime;
                this.taskCB = taskCB;
                this.cancelCB = cancelCB;
                this.startTime = startTime;

                loopIndex = 0;
            }
        }
    }
}